/**
 * Copyright (c) 2012-2014 All Rights Reserved.
 */
package com.easy.framwork.jdbc.handlers;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import com.easy.framwork.exceptions.ConvertException;
import com.easy.framwork.exceptions.ReflectException;
import com.easy.framwork.jdbc.JdbcUtils;
import com.easy.framwork.utils.ReflectUtils;
import com.easy.framwork.utils.StringUtils;
import com.easy.framwork.utils.bean.PropertyDescriptorsCache;

/**
 * 
 * @author wy
 * @version v 0.1 2014-1-2 下午9:31:14 wy Exp $
 * @param <T>
 */
public class ListBeanHandler<T> extends AbstractListHandler<T> {

    /** cache property */
    private PropertyDescriptorsCache cache;

    /** map the column and property */
    private Map<String, PropertyDescriptor> columnPropertyMap = new HashMap<String, PropertyDescriptor>();

    private Class<T> requiredType;

    /**
     * Create a new ListBeanHandler.
     * 
     * @param requiredType the type that each result object is expected to match
     */
    public ListBeanHandler(Class<T> requiredType) {
        this.requiredType = requiredType;
    }

    /**
     * Set the type that each result object is expected to match.
     * <p>
     * If not specified, the column value will be exposed as returned by the
     * JDBC driver.
     */
    public void setRequiredType(Class<T> requiredType) {
        this.requiredType = requiredType;
    }

    /**
     * @see com.easy.framwork.jdbc.handlers.AbstractListHandler#handleRow(java.sql.ResultSet,
     *      int)
     */
    @SuppressWarnings("unchecked")
    @Override
    protected T handleRow(ResultSet rs, int currentRow) throws SQLException {
        if (Object.class.equals(requiredType) || JdbcUtils.isJdbcType(requiredType)) {

            ResultSetMetaData rsmd = rs.getMetaData();
            int cols = rsmd.getColumnCount();
            if (cols > 1) {
                throw new SQLException("ResultSet column count than 1");
            }

            return (T) JdbcUtils.getResultSetValue(rs, 1, Object.class.equals(requiredType) ? null
                : requiredType);
        }

        if (cache == null) {
            cache = PropertyDescriptorsCache.forClass(requiredType);
        }

        ResultSetMetaData rsmd = rs.getMetaData();
        int cols = rsmd.getColumnCount();

        Object entity =  ReflectUtils.newInstance(requiredType);

        for (int i = 1; i <= cols; i++) {
            String columnName = rsmd.getColumnName(i);

            PropertyDescriptor pd = columnPropertyMap.get(columnName);
            if (pd == null) {
                pd = cache.getPropertyDescriptor(columnName);
                if (pd == null) {
                    String propertyName = StringUtils.toPropertyName(columnName);
                    pd = cache.getPropertyDescriptor(propertyName);
                }
                if (pd == null || pd.getWriteMethod() == null) {
                    continue;
                }
            }

            columnPropertyMap.put(columnName, pd);

            Method method = pd.getWriteMethod();

            Class<?> type = method.getParameterTypes()[0];
            if (!JdbcUtils.isJdbcType(type)) {
                log.warn("{} property {} is not jdbc type", requiredType.getName(), columnName);
                continue;
            }

            Object value = JdbcUtils.getResultSetValue(rs, i, type);

            try {
                if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
                    method.setAccessible(true);
                }
                method.invoke(entity, new Object[] { value });
            }
            catch (Exception e) {
                log.error("invoke class {}#method{} error,:{}",
                    new Object[] { requiredType, method.getName(), e });
                throw new ReflectException("instantiation class " + requiredType + "#method:"
                                        + method.getName() + "error");
            }

        }

        return (T) entity;
    }
}
